/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANIUTIL_ARRAY_H
#define ANIUTIL_ARRAY_H

#include <ani.h>

#include <securec.h>
#include <vector>

#include "class.h"
#include "primitive.h"

namespace OHOS {
namespace AniUtil {

template <typename T>
class ArrayAccessor {
public:
    ArrayAccessor(ani_env *env) : env_(env)
    {
    }

    ArrayAccessor(ani_env *env, ani_object obj) : env_(env), obj_(obj)
    {
    }

    expected<std::size_t, ani_status> Length()
    {
        ani_double value;
        if (ANI_OK != env_->Object_GetPropertyByName_Double(obj_, "length", &value)) {
            return ANI_ERROR;
        }
        return int(value);
    }

    template <class TransformFunc>
    expected<ani_object, ani_status> Pack(const std::vector<T>& data, TransformFunc&& transform)
    {
        return Pack(data.begin(), data.end(), transform);
    }

    template <class TransformFunc>
    expected<ani_object, ani_status> Pack(const uint8_t *nData, std::size_t nLength, TransformFunc&& transform)
    {
        return Pack(nData, nData + nLength, transform);
    }

    template <class InputIterator, class TransformFunc>
    expected<ani_object, ani_status> Pack(InputIterator first1, InputIterator last1, TransformFunc&& transform)
    {
        auto length = std::distance(first1, last1);
        auto result = ObjectFactory(env_).Create("Lescompat/Array;", length);
        if (!result.has_value()) {
            return result.error();
        }
        ani_status status = ANI_ERROR;
        ani_object arrayObj = result.value();
        ani_size index = 0;
        for (auto it = first1; it < last1; it++, index++) {
            auto objResult = transform(env_, *it);
            if (!objResult.has_value()) {
                return objResult.error();
            }
            status = env_->Object_CallMethodByName_Void(arrayObj, "$_set", "ILstd/core/Object;:V", index, objResult.value());
            if (ANI_OK != status) {
                return status;
            }
        }
        return arrayObj;
    }

    template <typename TransformFunc>
    expected<std::vector<T>, ani_status> Unpack(TransformFunc&& transform)
    {
        std::vector<T> data;
        ani_status status = Unpack(std::back_inserter(data), transform);
        if (ANI_OK != status) {
            return status;
        }
        return data;
    }

    template <typename OutputIterator, typename TransformFunc>
    ani_status Unpack(OutputIterator out, TransformFunc&& transform)
    {
        ani_status status = ANI_ERROR;
        auto lengthResult = Length();
        if (!lengthResult.has_value()) {
            return lengthResult.error();
        }
        for (std::size_t i = 0; i < lengthResult.value(); i++) {
            ani_ref itemRef;
            status = env_->Object_CallMethodByName_Ref(obj_, "$_get", "I:Lstd/core/Object;", &itemRef, (ani_int)i);
            if (ANI_OK != status) {
                return status;
            }
            auto result = transform(env_, static_cast<ani_object>(itemRef));
            if (!result.has_value()) {
                return result.error();
            }
            *out++ = result.value();
        }
        return ANI_OK;
    }

private:
    ani_env *env_ = nullptr;
    ani_object obj_ = nullptr;
};


template<>
class ArrayAccessor<uint8_t> {
public:
    ArrayAccessor(ani_env *env) : env_(env)
    {
    }

    ArrayAccessor(ani_env *env, ani_object obj) : env_(env), obj_(obj)
    {
    }

    ani_status Length(std::size_t &length)
    {
        ani_double value;
        if (ANI_OK != env_->Object_GetPropertyByName_Double(obj_, "length", &value)) {
            return ANI_ERROR;
        }
        length = int(value);
        return ANI_OK;
    }

    expected<ani_object, ani_status> Pack(const std::vector<uint8_t> &data)
    {
        return Pack(data.data(), data.size());
    }

    expected<ani_object, ani_status> Pack(const uint8_t *nData, std::size_t nLength)
    {
        return PackImpl([&](uint8_t *tsData, std::size_t tsLength) {
            auto ret = memcpy_s(const_cast<uint8_t*>(nData), nLength, tsData, tsLength);
            return (ret != 0) ? ANI_ERROR : ANI_OK;
        }, nLength);
    }

    template <class InputIterator>
    expected<ani_object, ani_status> Pack(InputIterator first1, InputIterator last1)
    {
        auto nLength = std::distance(first1, last1);
        return PackImpl([&](uint8_t *nData, std::size_t nLength) {
            std::copy(first1, last1, nData);
            return ANI_OK;
        }, nLength);
    }

    expected<std::vector<uint8_t>, ani_status> Unpack()
    {
        std::vector<uint8_t> data;
        ani_status status = Unpack(std::back_inserter(data));
        if (ANI_OK != status) {
            return status;
        }
        return data;
    }

    ani_status Unpack(uint8_t *nData, std::size_t nLength)
    {
        return UnpackImpl([&](uint8_t *tsData, std::size_t tsLength) {
            auto ret = memcpy_s(tsData, tsLength, nData, nLength);
            return (ret != 0) ? ANI_ERROR : ANI_OK;
        });
    }

    template <typename OutputIterator>
    ani_status Unpack(OutputIterator out)
    {
        return UnpackImpl([&](uint8_t *nData, std::size_t nLength) {
            std::copy(static_cast<uint8_t*>(nData), static_cast<uint8_t*>(nData) + nLength, out);
            return ANI_OK;
        });
    }

private:
    template <class Handle>
    expected<ani_object, ani_status> PackImpl(Handle &&handle, size_t length)
    {
        auto result = ObjectFactory(env_).Create("Lescompat/Uint8Array;", length);
        if (!result.has_value()) {
            return result.error();
        }

        ani_status status = ANI_ERROR;
        ani_object arrayObj = result.value();
        ani_ref buffer;
        env_->Object_GetFieldByName_Ref(arrayObj, "buffer", &buffer);
        void *bufData;
        size_t bufLength;
        status = env_->ArrayBuffer_GetInfo(static_cast<ani_arraybuffer>(buffer), &bufData, &bufLength);
        if (ANI_OK != status) {
            return status;
        }
        status = handle(static_cast<uint8_t *>(bufData), bufLength);
        if (ANI_OK != status) {
            return status;
        }
        return arrayObj;
    }

    template <class Handle>
    ani_status UnpackImpl(Handle &&handle)
    {
        ani_status status = ANI_ERROR;
        ani_ref buffer;
        status = env_->Object_GetFieldByName_Ref(obj_, "buffer", &buffer);
        if (ANI_OK != status) {
            return status;
        }
        void *data;
        size_t length;
        status = env_->ArrayBuffer_GetInfo(static_cast<ani_arraybuffer>(buffer), &data, &length);
        if (ANI_OK != status) {
            return status;
        }
        status = handle(static_cast<uint8_t *>(data), length);
        if (ANI_OK != status) {
            return status;
        }
        return ANI_OK;
    }

private:
    ani_env *env_ = nullptr;
    ani_object obj_ = nullptr;
};

} // namespace AniUtil
} // namespace OHOS

#endif
